/*!********************************************************************

 Audacity: A Digital Audio Editor

 @file FromChars.cpp
 @brief Define functions to convert numeric types to string representation.

 Dmitry Vedenko
 **********************************************************************/

#include "FromChars.h"

#include "3party/fast_float.h"

#include <limits>
#include <algorithm>

namespace {
// Converting c to unsigned here helps to eliminate one check
unsigned digitToInt(char c) noexcept
{
    return static_cast<unsigned>(c) - '0';
}

template<typename T>
bool safeMul10Add(T& result, T a, T b)
{
    static_assert(std::is_unsigned_v<T>);

    constexpr auto bits = sizeof(a) * 8 - 3;
    // Check for the top 3 bits of a
    if (a >> bits) {
        return false;
    }

    const T times8 = a << 3;
    // a << 1 won't overflow
    const T times2 = a << 1;

    // timesX are all unsigned values, if overflow occurs - times10 will be <
    // times8
    const T times10 = times8 + times2;

    if (times10 < times8) {
        return false;
    }

    const T tempResult = times10 + b;

    if (tempResult < times10) {
        return false;
    }

    result = tempResult;

    return true;
}

template<typename ResultType>
FromCharsResult FastStringToInt(
    const char* first, const char* last, ResultType& value,
    bool isNegative) noexcept
{
    using UnsignedResultType = std::make_unsigned_t<ResultType>;

    const auto availableBytes = last - first;

    if (availableBytes <= 0) {
        return { first, std::errc::invalid_argument };
    }

    UnsignedResultType result = digitToInt(*first);

    if (result > 10) {
        return { first, std::errc::invalid_argument };
    }

    constexpr auto maxSafeDigits = std::numeric_limits<ResultType>::digits10;

    const char* ptr = first;
    const char* safeLast
        =first + std::min<decltype(availableBytes)>(availableBytes, maxSafeDigits);

    unsigned d;

    // No integer overflow here
    while (++ptr < safeLast && (d = digitToInt(*ptr)) <= 9)
    {
        result = result * 10 + d;
    }

    // But here live dragons
    while (ptr < last && (d = digitToInt(*ptr)) <= 9)
    {
        if (!safeMul10Add<UnsignedResultType>(result, result, d)) {
            return { ptr, std::errc::result_out_of_range };
        }

        // Even if there were no unsigned overflow,
        // signed overflow is still possible
        if constexpr (std::is_signed_v<ResultType>) {
            const UnsignedResultType max
                =static_cast<UnsignedResultType>(
                      std::numeric_limits<ResultType>::max())
                  + (isNegative ? 1 : 0);

            if (result > max) {
                return { ptr, std::errc::result_out_of_range };
            }
        }

        ++ptr;
    }

    if constexpr (std::is_signed_v<ResultType>) {
        value = isNegative
                ? // Unary minus on unsigned type makes MSVC unhappy
                static_cast<ResultType>(0 - result)
                : static_cast<ResultType>(result);
    } else {
        value = static_cast<ResultType>(result);
    }

    return { ptr, std::errc() };
}

template<typename ResultType>
FromCharsResult
IntFromChars(const char* buffer, const char* last, ResultType& value) noexcept
{
    const char* origin = buffer;

    if (buffer >= last) {
        return { buffer, std::errc::invalid_argument };
    }

    const bool isNegative = *buffer == '-';

    if (isNegative) {
        if constexpr (std::is_signed_v<ResultType>) {
            ++buffer;
        } else {
            return { origin, std::errc::invalid_argument };
        }
    }

    const auto fastStringResult
        =FastStringToInt(buffer, last, value, isNegative);

    if (fastStringResult.ec == std::errc::invalid_argument) {
        return { origin, std::errc::invalid_argument };
    }

    return fastStringResult;
}
} // namespace

FromCharsResult FromChars(const char* buffer, const char* last, float& value) noexcept
{
    const auto result = fast_float::from_chars(buffer, last, value);
    return { result.ptr, result.ec };
}

FromCharsResult FromChars(const char* buffer, const char* last, double& value) noexcept
{
    const auto result = fast_float::from_chars(buffer, last, value);
    return { result.ptr, result.ec };
}

FromCharsResult FromChars(const char* buffer, const char* last, short& value) noexcept
{
    return IntFromChars(buffer, last, value);
}

FromCharsResult FromChars(const char* buffer, const char* last, unsigned short& value) noexcept
{
    return IntFromChars(buffer, last, value);
}

FromCharsResult FromChars(const char* buffer, const char* last, int& value) noexcept
{
    return IntFromChars(buffer, last, value);
}

FromCharsResult FromChars(const char* buffer, const char* last, unsigned int& value) noexcept
{
    return IntFromChars(buffer, last, value);
}

FromCharsResult FromChars(const char* buffer, const char* last, long& value) noexcept
{
    return IntFromChars(buffer, last, value);
}

FromCharsResult FromChars(const char* buffer, const char* last, unsigned long& value) noexcept
{
    return IntFromChars(buffer, last, value);
}

FromCharsResult FromChars(const char* buffer, const char* last, long long& value) noexcept
{
    return IntFromChars(buffer, last, value);
}

FromCharsResult FromChars(const char* buffer, const char* last, unsigned long long& value) noexcept
{
    return IntFromChars(buffer, last, value);
}

FromCharsResult
FromChars(const char* buffer, const char* last, bool& value) noexcept
{
    if (buffer >= last) {
        return { buffer, std::errc::invalid_argument };
    }

    if (buffer[0] == '0') {
        value = false;
        return { buffer + 1, std::errc() };
    } else if (buffer[0] == '1') {
        value = true;
        return { buffer + 1, std::errc() };
    }

    return { buffer, std::errc::invalid_argument };
}
